package impl

import (
	"errors"
	"github.com/gorilla/websocket"
	"sync"
)

type Connection struct {
	wsConn *websocket.Conn			// 被封装的WebSocket的连接
	inChan chan []byte				// 数据读入channel, 负责从客户端读取数据
	outChan chan []byte				// 数据输出channel, 负责写入数据到客户端
	closeChan chan byte				// 关闭通道
	mutex sync.Mutex				// 锁
	isClosed bool					// 通道是否已关闭
}

func InitConnection(wsConn *websocket.Conn) (conn *Connection, err error) {
	conn = &Connection{
		wsConn: wsConn,
		inChan: make(chan []byte, 1000),
		outChan: make(chan []byte, 1000),
		closeChan: make(chan byte, 1),
	}

	// 启动读协程
	go conn.readLoop()
	// 启动写协程
	go conn.writeLoop()

	return
}

///////////////////////////////////////////////// API /////////////////////////////////////////////////
func (conn *Connection) ReadMessage() (data []byte, err error) {
	select {
	// 可以从读通道中读取到数据，则返回读取到的数据
	case data = <- conn.inChan:
	// 无法从读通道中读取到数据，则抛出异常
	case <- conn.closeChan:
		err = errors.New("connection is closed")
	}

	return
}

func (conn *Connection) WriteMessage(data []byte) (err error) {
	select {
	case conn.outChan <- data:
	case <- conn.closeChan:
		err = errors.New("connection is closed")
	}

	return 
}

func (conn *Connection) Close() {
	// 线程安全，可重入的Close
	conn.wsConn.Close()

	// 这一行代码只执行一次
	conn.mutex.Lock()
	if !conn.isClosed {
		close(conn.closeChan)
		conn.isClosed = true
	}

	conn.mutex.Unlock()
}

///////////////////////////////////////////////// 内部实现 ////////////////////////////////////////////
func (conn *Connection) readLoop() {
	var (
		data []byte
		err error
	)

	for {
		if _, data, err = conn.wsConn.ReadMessage(); err != nil {
			goto ERR
		}

		// 阻塞在这里，等待inChan有空闲的位置
		select {
		case conn.inChan <- data:
		case <- conn.closeChan:
			// closeChan关闭的时候
			goto ERR
		}
	}

	ERR:
		conn.Close()
}

func (conn *Connection) writeLoop() {
	var (
		data []byte
		err error
	)

	for {
		select {
		case data = <- conn.outChan:
		case <- conn.closeChan:
			goto ERR
		}

		data = <- conn.outChan
		if err = conn.wsConn.WriteMessage(websocket.TextMessage, data); err != nil {
			goto ERR
		}
	}

	ERR:
		conn.Close()
}
